#!/usr/bin/perl
#
#	$HeadURL$
#	$Revision$
#	$Date$
#

use strict;
use warnings;

use IO::Socket::INET;
use Digest::MD5 qw(md5_hex);
use Digest::SHA qw(sha256_hex);
use Time::HiRes qw(gettimeofday);

sub usage {
	print "Usage: $0 <port> [password] [adminpass]\n";
	print "Usage: $0 stdin\n";
}

#	Print out the syntax if we don't get what we need.
unless (@ARGV && $ARGV[0]) {
	usage();
	exit 1;
}

#	Running configuration.
my $port = $ARGV[0] || 5000;
my $password = $ARGV[1];
my $reset_pw = $ARGV[2];

if ($ARGV[0] eq "stdin") {
	print("Enter TCP port: ");
	$port = <STDIN>;
	chomp($port);
	print("Enter TCP passwd: ");
	$password = <STDIN>;
	chomp($password);
	print("Enter TCP admin_passwd: ");
	$reset_pw = <STDIN>;
	chomp($reset_pw);
}

unless ($port && $password && $reset_pw) {
	usage();
	exit 1;
}

#	Status is a global, but can be changed later.
my $status = 'OK';
my $detail = '';

#	Some global variables
my %lnonce = qw();
my $extFuncName = "";

sensor_d(0);
exit 0;

#################################################################################
# The Console.pl should call this routine with
#     $flag = 1;
#     $d_port = the TCP port #
#     $d_password = user password
#     $d_reset_pw = the admin password
#     #ext_name = the function name which sits in Console.pl
#################################################################################
sub sensor_d {
	my($flag, $d_port, $d_password, $d_reset_pw, $ext_name) = @_;

	if ($flag == 1) {
		$port = $d_port;
		$password = $d_password;
		$reset_pw = $d_reset_pw;
		$extFuncName = $ext_name;
	}

	my $socket = listenSocket($port);
	selectSocket($socket);
}

#################################################################################
# Listen on local host 
#################################################################################
sub listenSocket {
	my($port) = @_;

	# Create a socket to listen on.
	my $socket = new IO::Socket::INET (
		#LocalHost => '127.0.0.1',
		LocalHost => '0.0.0.0',   #listen on all interfaces
		LocalPort =>  $port,
		Proto => 'tcp',
		Listen => 5,
		Reuse => 1
		) or die "FATAL: Could not create socket: $!\n";

	print "SEN: === Listen on port $port\n";
	return $socket;
}

#################################################################################
sub selectSocket {
	my($listen_socket) = @_;

	use IO::Select;
	my $select_socket = new IO::Select();

	if ($listen_socket) {
		$select_socket->add($listen_socket);
	}

	while (1) {
		my ($my_sock) = IO::Select->select($select_socket, undef, undef, 1);
		foreach my $sock (@$my_sock) {
			if ($sock == $listen_socket) {	#Accept new connection
				my $client_socket = $sock->accept();
				$client_socket->autoflush(1);
				$select_socket->add($client_socket);
				$lnonce{$client_socket} = undef;

				my $peer_address = $client_socket->peerhost();
				my $peer_port = $client_socket->peerport();
				print "SEN: === Accepted New Connection From $peer_address port $peer_port\n";
			}
			else {  #Ordinary client socket, read data
				my $data = <$sock>;
				if ($data) {   #Got data
					if (process_data($sock, $data)) {
						#Receive the QUIT msg so let's close socket
						my $peer_address = $sock->peerhost();
						my $peer_port = $sock->peerport();
						print "SEN: === (1)Close Connection with $peer_address port $peer_port\n";

						$select_socket->remove($sock);
						close($sock);
					}
				}
				else {  #Close socket
					my $peer_address = $sock->peerhost();
					my $peer_port = $sock->peerport();
					print "SEN: === (2)Close Connection with $peer_address port $peer_port\n";

					$select_socket->remove($sock);
					close($sock);
				}
			}
		}

		# Call the external subroutine
		if ((defined($extFuncName) && (length($extFuncName) > 0))) {
			eval "$extFuncName";  #execute a caller's subroutine
			if ($@) {
				print("SEN: === Failed to execute \"$extFuncName\": \"$@\".");
			}
		}
	}
}

#################################################################################
# Process the data packet 
#################################################################################
sub process_data {
	my($client_socket, $data) = @_;

	if ($data) {
		chomp $data;
		#print $client_socket "$data\n";
		print "SEN: Received from Client: $data\n";

		# Send the nonce.
		if ($data eq 'REQ_NONCE') {
			$lnonce{$client_socket}  = join '', gettimeofday();
			print "SEN: Sending: NONCE $lnonce{$client_socket}\n";
			print $client_socket "NONCE $lnonce{$client_socket}\n";
		}

		# Attempt to authenticate the user.
		if ($data =~ /^REQ_STATUS,user/) {
			my $nonce = $lnonce{$client_socket};
			my($rc, $cnonce) = authenticated($data, $nonce, $password);
			if ($rc) {
				#my $hash = md5_hex("STATUS $status ($detail)".$nonce.$cnonce.$password);
				my $hash = sha256_hex("STATUS $status ($detail)".$nonce.$cnonce.$password);

				print "SEN: Sending status: STATUS $status ($detail) ($nonce,$hash)\n";
				print $client_socket "STATUS $status ($detail),$nonce,$hash\n";
			} else {
				#my $hash = md5_hex("STATUS BAD AUTH".$nonce.$cnonce.$password);
				my $hash = sha256_hex("STATUS BAD AUTH".$nonce.$cnonce.$password);

				print "SEN: Sending status: Bad authentication ($nonce,$hash)\n";
				print $client_socket "STATUS BAD AUTH,$nonce,$hash\n";
			}
		}

		# Allow for setting detail, in the event that Booleans aren't enough.
		if ($data =~ /^SET_STATUS\((.*)\),user/) {
			my $reqDetail = $1;
			my $nonce = $lnonce{$client_socket};
			my($rc, $cnonce) = authenticated($data, $nonce, $password);
			if ($rc) {
				$detail = $reqDetail;

				#my $hash = md5_hex("STATUS $status ($detail)".$nonce.$cnonce.$password);
				my $hash = sha256_hex("STATUS $status ($detail)".$nonce.$cnonce.$password);

				print "SEN: Setting detail to |$detail| ($nonce,$hash)\n";
				print $client_socket "STATUS $status ($detail),$nonce,$hash\n";
			}
		}

		# Allow admin resets, will double for test apparatus.
		if ($data =~ /^TOGGLE_STATUS,user/) {
			my $nonce = $lnonce{$client_socket};
			my($rc, $cnonce) = authenticated($data, $nonce, $password);
			if ($rc) {
				my $oldStatus = $status;
				if ($oldStatus eq 'OK') {
					$status = 'BAD';
				} else {
					$status = 'OK';
				}

				#my $hash = md5_hex("STATUS $status ($detail)".$nonce.$cnonce.$password);
				my $hash = sha256_hex("STATUS $status ($detail)".$nonce.$cnonce.$password);

				print "SEN: Toggled status from $oldStatus to $status ($nonce,$hash).\n";
				print $client_socket "STATUS $status ($detail),$nonce,$hash\n";
			}
		}

		# Client is done with us, we can close.
		if ($data =~ /^QUIT/) {
			print "SEN: Closing client socket.\n";
			return 1;
		}
	}
	return 0;
}

#################################################################################
# Authenticate the user.
#################################################################################
sub authenticated {
	my $authString = shift;
	my $nonce = shift;
	my $password = shift;

	my ($cmd,$user,$cnonce,$chash) = split /,/, $authString;

	#	We need all of the arguments intact, else we must fail.
	unless ($cmd && $user && $cnonce && $chash && $nonce && $password) {
		return (0, 0);
	}

	#	Calculate what we think the hash ought to be.
	#my $hash = md5_hex("$cmd $user".$nonce.$cnonce.$password);
	my $hash = sha256_hex("$cmd $user".$nonce.$cnonce.$password);

	if ($hash eq $chash) {
		print "SEN: Authenticated as $user.\n";
		return(1, $cnonce);
	}
	return(0, $cnonce);
}

# EOF
